feat(ui): add CTA card side column to blog posts - #79
Merged
matej-marcisovsky merged 1 commit intoJul 31, 2026
Conversation
Blog posts rendered as a single centred article, so the only promo surface was an inline section at the bottom that readers reach only after finishing the piece. This adds a side column beside the article for marketing CTAs. Content model: - `cards.cta-card` is a generic, reusable component (title, optional image, description, one link, primary/secondary button). It is named for its structure rather than a use case, so an editor can make it a demo promo, a sales prompt or a docs link without a schema change, and it can be dropped into any dynamic zone. - The link is the existing `utilities.link`, so a CTA points at either an internal page relation or an external URL with `newTab` — the same choice the hero section already offers, with the same conditional fields. - `sidebar` is a repeatable `cards.cta-card` on both the Blog single type and Blog Post. The single type holds the blog-wide default; a post overrides it outright when it sets its own. Evergreen cards would not survive being re-added to every post by hand, and new posts would otherwise publish with an empty column. Layout: - Article and column are centred as one group inside the standard page container, so the column's right edge lands on the same grid as the hero and navbar. The share rail is now anchored to the article rather than a viewport offset that only cleared above 1536px. - The column shows from xl and sticks on scroll like the share rail. Below xl the cards flow into the article width instead of vanishing or keeping the rail's narrow column. - With a column present the article narrows from 864px to 616px, which is what lets four tracks fit the grid. Posts without a sidebar are unchanged. Generated types were hand-written in typegen's format because `strapi ts:generate-types` currently fails on main with "e.charAt is not a function"; they should be regenerated once that is fixed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
@PaulBratslavsky is attempting to deploy a commit to the Strapi-Website Team on Vercel. A member of the Team first needs to authorize it. |
matej-marcisovsky
approved these changes
Jul 31, 2026
Contributor
Author
|
@matej-marcisovsky thanks for the review. I don't have auth to merge or deploy, so if it looks good, can you push it. 🙂 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What & why
Blog posts render as a single centred article, so the only promo surface is an inline section at the bottom — which readers reach only after finishing the piece. This adds a side column beside the article for marketing CTAs.
Content model
cards.cta-card— a generic, reusable component: title, optional image, description, one link, and aprimary | secondarybutton style.It is deliberately named for its structure, not a use case. An editor can make it a demo promo, a sales prompt, a webinar invite or a docs link with no schema change, and it can be dropped into any dynamic zone — not just the blog.
The link reuses the existing
utilities.link, so a CTA points at either an internal page relation or an external URL withnewTab— the same choice the hero section already offers, with the same conditional fields (hrefshows forexternal, the page picker forpage).Two-tier placement.
sidebaris a repeatablecards.cta-cardon both:api::blog.blog(Blog single type)api::blog-post.blog-postThe single type is the primary home because the cards are evergreen. Re-adding them to every post by hand would not survive contact with a real content team, and new posts would otherwise publish with an empty column. A post that sets its own column replaces the default outright rather than merging — merge order would be ambiguous and invisible to the editor.
Layout
left-full) instead of a viewport offset (left-[calc(50%+27rem+1rem)]) that only cleared above 1536px.xland sticks on scroll, like the share rail.xlthe cards flow into the article width rather than vanishing or keeping the rail's narrow column.Tradeoff: with a column present the article narrows from 864px to 616px — that is what lets four tracks fit the page grid. Posts without a sidebar are completely unchanged.
Testing
Verified locally against Strapi + Next: both link modes resolve correctly (
type: page→ the page'sfullPath,type: external→ the URL withtarget="_blank" rel="noopener noreferrer"), cards render beside the article and not below it, the image sits between heading and button, the column sticks through a long scroll, cards fall inline belowxl, and there is no horizontal overflow at any breakpoint.tsc --noEmitand ESLint are clean on all changed files.Note for reviewers
apps/strapi/types/generated/*was hand-written in typegen's format becausepnpm generate:typescurrently fails onmainwithe.charAt is not a function— it fails on a clean checkout too, so it predates this branch. Those files should be regenerated once that is fixed.🤖 Generated with Claude Code